package lzupdater.net;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import lzupdater.log.Logger;
import lzupdater.log.LoggerLevel;
import lzupdater.utils.Encrypter;

//LZRosterUpdater, a java client to update LZRoster
//Copyright (C) 2008 Fraskor from Krasus (e-mail: fraskor@gmail.com)

//This file is part of LZRosterUpdater.

//LZRosterUpdater is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program.  If not, see <http://www.gnu.org/licenses/>.

/**
 * 
 * @author Fraskor
 */
public class PostData {

	private Hashtable<String, Object> data = new Hashtable<String, Object>();
	private boolean fileIsPresent;
	private PostOutStream out;

	private static String dfltEncName = "UTF-8";

	protected static String generateBoundary() {
		return String.format("----------------------------------%d", System
				.nanoTime());
	}

	public PostData(PostElement... pes) {
		this.setData(pes);
	}

	public void setData(PostElement[] pes) {
		for (PostElement pe : pes) {
			this.setData(pe);
		}
	}

	public void setData(PostElement element) {
		if (element.getValue() instanceof File) {
			fileIsPresent = true;
		}
		data.put(element.getKey(), element.getValue());
	}

	public Object removeData(String key) {
		return data.remove(key);
	}

	public void clear() {
		data.clear();
		fileIsPresent = false;
	}

	public void dump(Logger logger) {
		for (Entry<String, Object> entry : data.entrySet()) {
			Object v = entry.getValue();
			if (entry.getKey().equalsIgnoreCase("password") && v != null) {
				v = Encrypter.getMaskedPassword(v.toString());
			}
			logger.format(LoggerLevel.DEBUG, " Post[%s]='%s'", entry.getKey(),
					v);
		}
	}

	public void sendPostData(HttpURLConnection connection)
	throws ProtocolException, IOException {
		connection.setDoOutput(true);
		connection.setUseCaches(false);
		connection.setRequestMethod("POST");
		if (true || fileIsPresent) {
			sendPostDataAsMultipart(connection);
		} else {
			sendPostDataAsSinglepart(connection);
		}
		connection.getOutputStream().close();
	}
	private void sendPostDataAsMultipart(HttpURLConnection connection)
	throws IOException {
		String boundary = generateBoundary();
		connection.setRequestProperty("Content-Type",
				"multipart/form-data; boundary=" + boundary);

		out = new PostOutStream(connection.getOutputStream(), boundary);
		out.writeln();
		for (Map.Entry<String, Object> entry : data.entrySet()) {
			out.writeParametre(entry.getKey(), entry.getValue());
		}
	}

	private void sendPostDataAsSinglepart(HttpURLConnection connection)
	throws IOException {
		String parametres = getPostDataString();
		byte[] data = parametres.getBytes();

		connection.setRequestProperty("Content-type",
		"application/x-www-form-urlencoded");
		connection.setRequestProperty("Content-length", Integer
				.toString(data.length));

		OutputStream out = connection.getOutputStream();
		out.write(data);
		out.flush();
	}

	private String getPostDataString() throws UnsupportedEncodingException {
		StringBuffer resultat = new StringBuffer();

		for (Iterator<Map.Entry<String, Object>> it = data.entrySet()
				.iterator(); it.hasNext();) {
			Map.Entry<String, Object> entry = it.next();
			resultat.append(URLEncoder.encode(entry.getKey(), dfltEncName));
			resultat.append("=");
			resultat.append(URLEncoder.encode(entry.getValue().toString(),
					dfltEncName));
			if (it.hasNext()) {
				resultat.append("&");
			}
		}
		return resultat.toString();
	}

	private static String encode(String s) throws UnsupportedEncodingException {
		return URLEncoder.encode(s, dfltEncName);
	}

	public static class PostOutStream {
		private OutputStream os;
		private String boundary;

		public PostOutStream(OutputStream os, String boundary) {
			this.os = os;
			this.boundary = "--" + boundary;
		}

		private void boundary() throws IOException {
			writeln(boundary);
		}

		private void write(String... ss) throws IOException {
			if (ss != null && ss.length > 0) {
				for (String s : ss) {
					os.write(s.getBytes());
				}
			}
		}

		private void writeln(String... ss) throws IOException {
			write(ss);
			write("\r\n");
		}

		private void writeName(String name) throws IOException {
			writeln("Content-Disposition: form-data; name=\"", PostData
					.encode(name), "\"");
		}

		private void writeParametre(String name, Object value)
		throws IOException {
			if (value instanceof File) {
				File data = (File) value;
				writeFile(name, data.getName(), new FileInputStream(data));
			} else {
				writeData(name, value.toString());
			}
		}

		private void writeData(String name, String value) throws IOException {
			boundary();
			writeName(name);
			writeln();
			writeln(PostData.encode(value));
		}

		private void writeFile(String name, String filename, InputStream in)
		throws IOException {

			boundary();
			writeName(name);
			writeln("; filename=\"", PostData.encode(filename), "\"");
			write("Content-Type: ");
			String type = URLConnection.guessContentTypeFromName(filename);
			if (type == null)
				type = "application/octet-stream";
			writeln(type);
			writeln();
			byte[] buf = new byte[Math.max(1, in.available())];
			int nread;
			synchronized (in) {
				while ((nread = in.read(buf, 0, buf.length)) >= 0) {
					os.write(buf, 0, nread);
				}
			}

			os.flush();
			writeln();
		}

		public String getBoundary() {
			return boundary;
		}

	}
}
